#include "nim-server.h"

using namespace std;

int main(int argc, char** argv) {

	int sock;
	int status;
	struct server_parameters params;

	status = parseCommandLineArguments(argc, (const char**)((argv)), params);
    if (status == ARGS_ERROR) {
		cout << "nim-server: Usage: nim-server p N isMisere [port]\n";
		cout << "nim-server: where (2 <= p <= 9), (1 <= N <= 2000) and isMisere is 0 or 1\n";
		exit(-1);
	}

    sock = setupServer(params.portNumber);
    if (sock == CONNECTION_ERROR) {
		cout << "nim-server: Unexpected error occured while setting up the server\n";
		exit(-1);
	}

    struct game_state gameState;
    initGameState(gameState, params);

    status = manageGame(sock, gameState);
	if (status == GAME_END_CONNECTION_ERROR) {
		cout << "nim-server: Unexpected connection error occured. cannot continue\n";
		close(sock);
		destroyGameState(gameState);
		exit(-1);
	}

	close(sock);
	destroyGameState(gameState);

	return 0;
}

/* parsing */

int parseCommandLineArguments(int argc, const char** argv, struct server_parameters& params) {

	if (argc < 4) {
		return ARGS_ERROR;
	}

	int numberOfPlayers = atoi(argv[1]);
	if (numberOfPlayers < MIN_NUMBER_OF_PLAYERS || numberOfPlayers > MAX_NUMBER_OF_PLAYERS) {
		return ARGS_ERROR;
	}

	params.numberOfPlayers = numberOfPlayers;

	int heapSizeLimit = atoi(argv[2]);

	if (heapSizeLimit < 1 || heapSizeLimit > 2000) {
		return ARGS_ERROR;
	}

	params.heapSizeLimit = heapSizeLimit;

	int isMisere = atoi(argv[3]);

	if (isMisere != 0 && isMisere != 1) {
		return ARGS_ERROR;
	}

	params.isMisere = isMisere ? true : false;

	int portNumber;
	if (argc >= 5) {
		portNumber = atoi(argv[4]);
		if (portNumber < 0 || portNumber > MAX_PORT_NUMBER) {
			return ARGS_ERROR;
		}
	} else {
		portNumber = DEFAULT_PORT;
	}
	params.portNumber = portNumber;

	return ARGS_VALID;
}

/* socket setup */

int setupServer(int portNumber) {

	int status;

	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1) {
		return CONNECTION_ERROR;
	}

	sockaddr_in address;

	address.sin_family = AF_INET;
	address.sin_port = htons(portNumber);

	struct in_addr inAddr;
	inAddr.s_addr = htonl(INADDR_ANY);
	address.sin_addr = inAddr;

	status = bind(sock, (struct sockaddr*)&address, sizeof(address));
	if (status == -1) {
		close(sock);
		return CONNECTION_ERROR;
	}

	status = listen(sock, NUMBER_OF_LISTEN_CONNECTIONS);
	if (status == -1) {
		close(sock);
		return CONNECTION_ERROR;
	}

	return sock;
}

void initGameState(struct game_state& gameState, struct server_parameters& params) {
	memset(gameState.clientSockets, -1, (MAX_NUMBER_OF_TOTAL_CONNECTIONS + 1) * sizeof(int));
	memset(gameState.connectionStatus, CONNECTION_DISCONNECTED_STATUS, (MAX_NUMBER_OF_TOTAL_CONNECTIONS + 1) * sizeof(byte));
	memset(gameState.heaps,-1, NUMBER_OF_HEAPS * sizeof(int32_t));
	memset(gameState.reciveTranmissions, 0, (MAX_NUMBER_OF_TOTAL_CONNECTIONS + 1) * sizeof(Transmission*));

	gameState.heapSizeLimit = params.heapSizeLimit;
	gameState.isMisere = params.isMisere;
	gameState.numberOfPlayers = (byte)params.numberOfPlayers;
	gameState.numberOfClients = 0;
	gameState.nextClientId = 1;
	gameState.roundBeginningNotificationSent = false;
	gameState.gameEnded = false;
}

void destroyGameState(struct game_state& gameState) {
	gameState.players.clear();

	map<int,Transmission*>::iterator it = gameState.pendingRejectedClients.begin();
	while(it != gameState.pendingRejectedClients.end()) {
		close(it->first);
		delete it->second;
		it++;
	}
	gameState.pendingRejectedClients.clear();

	for (int i=1; i<=MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
		if (isConnected(gameState, i)) {
			close(gameState.clientSockets[i]);
		}
		delete gameState.reciveTranmissions[i];
		while (!gameState.sendTranmissions[i].empty()) {
			Transmission* transmission = gameState.sendTranmissions[i].front();
			gameState.sendTranmissions[i].pop_front();
			delete transmission;
		}
	}
}

/* game manager */
int manageGame(int sock, struct game_state& gameState) {

    populateWithRandomHeaps(gameState);

    while (!gameState.gameEnded || isPendingSends(gameState)) {

    	fd_set receiveFds;
    	fd_set sendFds;
    	FD_ZERO(&receiveFds);
    	FD_ZERO(&sendFds);

    	struct timeval timeout;
    	timeout.tv_sec = SELECT_TIMEOUT;
    	timeout.tv_usec = 0;

    	int maxSocket = sock;
    	for (int i = 1; i <= MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
    		if (isConnected(gameState, i)) {
    			maxSocket = max(maxSocket, gameState.clientSockets[i]);

    			// Receive:
    			if (!gameState.gameEnded) {
					FD_SET(gameState.clientSockets[i], &receiveFds);
    			}

    			// Send:
    			if (!gameState.sendTranmissions[i].empty()) {
    				FD_SET(gameState.clientSockets[i], &sendFds);
    			}
    		}
    	}

    	if (!gameState.gameEnded) {
    		FD_SET(sock, &receiveFds);
    	}

    	map<int, Transmission*>::iterator it = gameState.pendingRejectedClients.begin();
    	while(it != gameState.pendingRejectedClients.end()) {
    		maxSocket = max(maxSocket, it->first);
    		FD_SET(it->first, &sendFds);
    		it++;
    	}

    	int status = select(maxSocket+1, &receiveFds, &sendFds, NULL, &timeout);
    	if (status == -1) {
    		return GAME_END_CONNECTION_ERROR;
    	}

    	if (status == 0) {
    		continue;
    	}

    	if (!gameState.gameEnded && FD_ISSET(sock, &receiveFds)) {
    		int clientSock = acceptNewConnection(sock);
    		if (clientSock == CONNECTION_ERROR) {
    			// Something happended with new connection.. not much we can do about this.
    			// we'll be optimistic about the general state of the server for the sake of other
    			// clients
    		} else if (gameState.numberOfClients >= MAX_NUMBER_OF_CLIENTS) {
    			handleRejectedClient(clientSock, gameState);
    		} else {
    			handleNewClient(clientSock, gameState);
    		}
    	}

    	for (int i = 1; i <= MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
    		if (isConnected(gameState, i)) {

    			if (!gameState.gameEnded && FD_ISSET(gameState.clientSockets[i], &receiveFds)) {
    				int status = receiveTrasnmission(&gameState.reciveTranmissions[i],gameState.clientSockets[i],gameState,i);
    				if (status == CONNECTION_ERROR || status == CONNECTION_CLIENT_DISCONNECTION) {
    					// we'll be optimistic regarding the general state of the server
    					// and simply remove this client from the server
    					removeClient(gameState, i);
    				}
    			}

    			// checking if user is still connected because things might have change during recv
    			// from clean/force disconnection
    			if (isConnected(gameState, i) && FD_ISSET(gameState.clientSockets[i], &sendFds)) {

    				Transmission* transmission = gameState.sendTranmissions[i].front();
    				int status = sendTrasnmission(*transmission, gameState.clientSockets[i]);
    				if (status == CONNECTION_ERROR) {
    					// we'll be optimistic regarding the general state of the server
    					// and simply remove this client from the server
    					removeClient(gameState,i);
    				} else if (status == COMPLETED) {
    					if (transmission->getOpcode() == OPCODE_ACK) {
    						switchCurrentPlayer(gameState);
    						gameState.roundBeginningNotificationSent = false;
    					}
    					gameState.sendTranmissions[i].pop_front();
    					delete transmission;
    				}
    			}
    		}
    	}

    	if (!gameState.gameEnded && !gameState.roundBeginningNotificationSent &&
    		 gameState.numberOfClients > 0) {
    		handleRoundBeginning(gameState);
    	}

   		list<int> eraseSockets;
    	it = gameState.pendingRejectedClients.begin();
    	while(it != gameState.pendingRejectedClients.end()) {
    		int rejectedSock = it->first;
    		if (FD_ISSET(rejectedSock, &sendFds)) {
    			Transmission* transmission = gameState.pendingRejectedClients[rejectedSock];
				int status = sendTrasnmission(*transmission, rejectedSock);
				if (status == CONNECTION_ERROR) {
					eraseSockets.push_back(it->first);
				} else if (status == COMPLETED) {
					eraseSockets.push_back(it->first);
				}
    		}
    		it++;
    	}
		while(!eraseSockets.empty()) {
			int currentSock = eraseSockets.front();
			close(currentSock);
			gameState.pendingRejectedClients.erase(currentSock);
			eraseSockets.pop_front();
		}
    }

    return GAME_END_VALID;
}

int receiveTrasnmission(Transmission** transmission, int sock, struct game_state& gameState, byte clientId) {
	if (!*transmission) {
		byte opcode;
		int status = recv(sock, &opcode, 1, 0);

		if (status == -1) {
			return CONNECTION_ERROR;
		} else if (status == 0) {
			return CONNECTION_CLIENT_DISCONNECTION;
		}

		int msgLength = getMessageLengthByOpCode(opcode);
		if (msgLength == 0) {
			handleClientMessage(opcode, 0, gameState, clientId);
			return COMPLETED;
		} else {
			bool isMsgOpcode = opcode == OPCODE_CLIENT_MESSAGE;
			*transmission = new Transmission(opcode, isMsgOpcode);
			if (!isMsgOpcode) {
				(*transmission)->setTotalBytes(getMessageLengthByOpCode(opcode));
			}
		}
	}

	if ((*transmission)->isOngoingTransmission()) {
		int received = recv(sock, (*transmission)->getBuffer() + (*transmission)->getCompleteBytes(), (*transmission)->getRemainingBytes(), 0);
		if (received == -1) {
			return CONNECTION_ERROR;
		} else if (received == 0) {
			return CONNECTION_CLIENT_DISCONNECTION;
		}
		(*transmission)->incCompleteBytes(received);
	} else {
		// This case is only for client chat message when we still don't know the total length:
		int msgLength = getMessageLengthByOpCode(OPCODE_CLIENT_MESSAGE);
		int remainingLength = msgLength - (*transmission)->getCompleteBytes();

		int received = recv(sock, (*transmission)->getBuffer() + (*transmission)->getCompleteBytes(), remainingLength, 0);
		if (received == -1) {
			return CONNECTION_ERROR;
		} else if (received == 0) {
			return CONNECTION_CLIENT_DISCONNECTION;
		}
		(*transmission)->incCompleteBytes(received);

		if (received == remainingLength) { // Finished receiving message length
			int32_t totalBytes;
			memcpy(&totalBytes,(*transmission)->getBuffer()+1,sizeof(int32_t));
			(*transmission)->setTotalBytes(totalBytes + msgLength);
		}
	}

	if ((*transmission)->isComplete()) {
		handleClientMessage((*transmission)->getOpcode(), (*transmission)->getBuffer(), gameState, clientId);
		delete (*transmission);
		*transmission = 0;
		return COMPLETED;
	}
	return NOT_COMPLETED;
}

int sendTrasnmission(Transmission& transmission, int sock) {
	int sentBytes = send(sock, transmission.getBuffer() + transmission.getCompleteBytes(), transmission.getRemainingBytes(), 0);
	if (sentBytes == -1) {
		return CONNECTION_ERROR;
	}

	transmission.incCompleteBytes(sentBytes);
	return transmission.isComplete() ? COMPLETED : NOT_COMPLETED;
}

int acceptNewConnection(int sock) {
    int new_sock;
    sockaddr_in their_address;
    int sin_size = sizeof (struct sockaddr_in);
    new_sock = accept(sock, (struct sockaddr*)&their_address, (socklen_t*)&sin_size);
    if(new_sock == -1) {
        cout << "nim-server: Unexpected error occured while waiting for new connection\n";
        close(sock);
        return CONNECTION_ERROR;
    }
    return new_sock;
}

/* handlers */

void handleRejectedClient(int rejectedSock, struct game_state& gameState) {
	Transmission* transmission = new Transmission(OPCODE_CLIENT_REJECTED, false);
	transmission->setTotalBytes(OPCODE_CLIENT_REJECTED_LENGTH);
	createClientRejectedMessage(transmission->getBuffer());
	gameState.pendingRejectedClients[rejectedSock] = transmission;
}

void handleNewClient(int clientSock, struct game_state& gameState) {

	Transmission* gameTypeTransmission = new Transmission(OPCODE_GAME_TYPE, false);
	gameTypeTransmission->setTotalBytes(OPCODE_GAME_TYPE_LENGTH);
	createGameTypeMessage(gameTypeTransmission->getBuffer(), gameState.isMisere);

	int clientId = gameState.nextClientId;
	gameState.nextClientId += 1;
	gameState.clientSockets[clientId] = clientSock;
	gameState.numberOfClients += 1;

	gameState.sendTranmissions[clientId].push_back(gameTypeTransmission);

	Transmission* playerStatusTransmission = new Transmission(OPCODE_PLAYER_STATUS, false);
	playerStatusTransmission->setTotalBytes(OPCODE_PLAYER_STATUS_LENGTH);
	createPlayerStatusMessage(playerStatusTransmission->getBuffer(), gameState.numberOfPlayers, clientId);
	gameState.sendTranmissions[clientId].push_back(playerStatusTransmission);

	bool isPlayer = ((byte)gameState.players.size() < gameState.numberOfPlayers);
	gameState.connectionStatus[clientId] = isPlayer ? CONNECTION_PLAYER_STATUS : CONNECTION_VIEWER_STATUS;
	if (isPlayer) {
		gameState.players.push_back(clientId);
	}

	Transmission* playerGameStatusTransmission = new Transmission(OPCODE_PLAYER_GAME_STATUS, false);
	playerGameStatusTransmission->setTotalBytes(OPCODE_PLAYER_GAME_STATUS_LENGTH);
	createPlayerGameStatusMessage(playerGameStatusTransmission->getBuffer(), isPlayer ?
			CONNECTION_PLAYER_STATUS : CONNECTION_VIEWER_STATUS);
	gameState.sendTranmissions[clientId].push_back(playerGameStatusTransmission);

	if (gameState.numberOfClients > 1) {
		handleStatusMessages(gameState, clientId);
	}

}

/* clientId == 0 == BROADCAST */
void handleStatusMessages(struct game_state& gameState, int clientId) {
	Transmission* statusMessageTransmission = new Transmission(OPCODE_STATUS, false);
	statusMessageTransmission->setTotalBytes(OPCODE_STATUS_LENGTH);
	createStatusMessage(statusMessageTransmission->getBuffer(), gameState.heaps);

	Transmission* sentTransmission;

	if (clientId == 0) {
		for (int i=1; i<=MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
			if (isConnected(gameState, i)) {
				sentTransmission = new Transmission(*statusMessageTransmission);
				gameState.sendTranmissions[i].push_back(sentTransmission);
			}
		}
	} else {
		sentTransmission = new Transmission(*statusMessageTransmission);
		gameState.sendTranmissions[clientId].push_back(sentTransmission);
	}

	delete statusMessageTransmission;
}

void handleRoundBeginning(struct game_state& gameState) {
	gameState.roundBeginningNotificationSent = true;
	handleStatusMessages(gameState, 0);
	handleYourTurn(gameState);
}

void handleYourTurn(struct game_state& gameState) {
	Transmission* yourTurnTransmission = new Transmission(OPCODE_YOUR_TURN, false);
	int currentPlayer = gameState.players.front();
	yourTurnTransmission->setTotalBytes(OPCODE_YOUR_TURN_LENGTH);
	createYourTurnMessage(yourTurnTransmission->getBuffer());
	gameState.sendTranmissions[currentPlayer].push_back(yourTurnTransmission);
}

void handleGameEnd(struct game_state& gameState, byte lastPlayer) {
	gameState.gameEnded = true;

	handleStatusMessages(gameState, 0);

	bool lastPlayerWinning = !gameState.isMisere;

	Transmission* gameEndStatusTransmission;
	for (int i=1; i<=MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
		if (isConnected(gameState, i)) {
			gameEndStatusTransmission = new Transmission(OPCODE_GAME_END_STATUS, false);
			gameEndStatusTransmission->setTotalBytes(OPCODE_GAME_END_STATUS_LENGTH);

			int status;
			if (i == lastPlayer) {
				status = lastPlayerWinning ? GAME_END_STATUS_WINNER : GAME_END_STATUS_LOSER;
			} else if (isPlayer(gameState,i)) {
				status = lastPlayerWinning ? GAME_END_STATUS_LOSER : GAME_END_STATUS_WINNER;
			} else {
				status = GAME_END_STATUS_VIEWER;
			}

			createGameEndStatusMessage(gameEndStatusTransmission->getBuffer(), status);
			gameState.sendTranmissions[i].push_back(gameEndStatusTransmission);
		}
	}
}


void handleClientMessage(int opCode, char buffer[], struct game_state& gameState, byte clientId) {

	switch(opCode) {
	case OPCODE_CLIENT_MOVE:
		if (isClientTurn(gameState, clientId)) {
			handleClientMoveMessage(buffer, gameState, clientId);
		} else {
			handleClientNotInTurnMessage(buffer, gameState, clientId);
		}
		break;
	case OPCODE_CLIENT_ILLEGAL_MOVE:
		if (isClientTurn(gameState, clientId)) {
			handleClientIllegalMoveMessage(buffer, gameState, clientId);
		} else {
			handleClientNotInTurnMessage(buffer, gameState, clientId);
		}
		break;
	case OPCODE_CLIENT_MESSAGE:
		handleClientChatMessage(buffer, gameState, clientId);
		break;
	case OPCODE_CLIENT_QUIT:
		handleClientQuitMessage(buffer, gameState, clientId);
		break;
	default:
		handleClientInvalidMessage(buffer, gameState, clientId);
		break;
	}

}

void handleClientMoveMessage(char buffer[], struct game_state& gameState, byte clientId) {

	char rawChosenHeap = buffer[0];
	int chosenHeap = rawChosenHeap - 'A';

	int32_t numberOfRemovedObjects = 0;
	memcpy(&numberOfRemovedObjects, buffer+1, sizeof(int32_t));

	Transmission* ackTransmission = new Transmission(OPCODE_ACK, false);
	ackTransmission->setTotalBytes(OPCODE_ACK_LENGTH);

	bool legalMove = isLegalMove(gameState,chosenHeap,numberOfRemovedObjects);
	createAckMessage(ackTransmission->getBuffer(), legalMove);

	if (legalMove) {
		gameState.heaps[chosenHeap] -= numberOfRemovedObjects;
	}

	gameState.sendTranmissions[(int)clientId].push_back(ackTransmission);

	if (isGameEnd(gameState)) {
		handleGameEnd(gameState, clientId);
	}

}

void handleClientIllegalMoveMessage(char buffer[], struct game_state& gameState, byte clientId) {

	Transmission* ackTransmission = new Transmission(OPCODE_ACK, false);
	ackTransmission->setTotalBytes(OPCODE_ACK_LENGTH);
	createAckMessage(ackTransmission->getBuffer(), false);
	gameState.sendTranmissions[(int)clientId].push_back(ackTransmission);

}

void handleClientChatMessage(char buffer[], struct game_state& gameState, byte clientId) {

	byte recipient = buffer[0];

	if (recipient < 0 || recipient > MAX_NUMBER_OF_TOTAL_CONNECTIONS) {
		return;
	}

	if (recipient > 0 && !isConnected(gameState, recipient)) {
		return;
	}

	int32_t messageLength;
	memcpy(&messageLength, buffer+1, sizeof(int32_t));

	if (messageLength < 1 || messageLength > MAX_MESSAGE_SIZE - OPCODE_CLIENT_MESSAGE_HEADER_LENGTH) {
		return;
	}

	Transmission* chatTransmission = new Transmission(OPCODE_SERVER_MESSAGE, true);
	chatTransmission->setTotalBytes(OPCODE_CLIENT_MESSAGE_HEADER_LENGTH+messageLength);
	createServerChatMessage(chatTransmission->getBuffer(), clientId, messageLength,
			buffer + 1 + sizeof(int32_t));

	Transmission* sentMessage;
	if (recipient == 0) {
		for (int i=1; i<=MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
			if (i != clientId && isConnected(gameState,i)) {
				sentMessage = new Transmission(*chatTransmission);
				gameState.sendTranmissions[i].push_back(sentMessage);
			}
		}
	} else {
		sentMessage = new Transmission(*chatTransmission);
		gameState.sendTranmissions[(int)recipient].push_back(sentMessage);
	}

	delete chatTransmission;

}

void handleClientQuitMessage(char buffer[], struct game_state& gameState, byte clientId) {
	removeClient(gameState, clientId);
}

void handleClientInvalidMessage(char buffer[], struct game_state& gameState, byte clientId) {
	if (isClientTurn(gameState, clientId)) {
		handleClientIllegalMoveMessage(buffer, gameState, clientId);
	} else {
		handleClientNotInTurnMessage(buffer, gameState, clientId);
	}
}

void handleClientNotInTurnMessage(char buffer[], struct game_state& gameState, byte clientId) {

	Transmission* notInTurnTransmission = new Transmission(OPCODE_ILLEGAL_NOT_IN_TURN_MESSAGE, false);
	notInTurnTransmission->setTotalBytes(OPCODE_ILLEGAL_NOT_IN_TURN_MESSAGE_LENGHT);
	createIllegalNotInTurnMessage(notInTurnTransmission->getBuffer());
	gameState.sendTranmissions[(int)clientId].push_back(notInTurnTransmission);

}

bool isClientTurn(struct game_state& gameState, byte clientId) {
	return (gameState.players.front() == clientId);
}

bool isPlayer(struct game_state& gameState, byte clientId) {
	return (gameState.connectionStatus[(int)clientId] == CONNECTION_PLAYER_STATUS);
}

bool isConnected(struct game_state& gameState, byte clientId) {
	return (gameState.connectionStatus[(int)clientId] != CONNECTION_DISCONNECTED_STATUS);
}

bool isPendingSends(struct game_state& gameState) {
	for (int i=1; i<=MAX_NUMBER_OF_CLIENTS; i++) {
		if (!gameState.sendTranmissions[i].empty()) {
			return true;
		}
	}
	return false;
}

void switchCurrentPlayer(struct game_state& gameState) {
	byte clientId = gameState.players.front();
	gameState.players.pop_front();
	gameState.players.push_back(clientId);
}

void removeClient(struct game_state& gameState, byte clientId) {

	if (isPlayer(gameState, clientId) && !gameState.gameEnded) {

		bool pendingViewer =
				gameState.numberOfPlayers == (int)gameState.players.size() &&
				gameState.numberOfClients > (int)gameState.players.size();

		if (isClientTurn(gameState, clientId)) {
			switchCurrentPlayer(gameState);
			handleYourTurn(gameState);
		}

		list<byte>::iterator i = gameState.players.begin();
		for (; i != gameState.players.end() && *i != clientId; i++);

		if (i != gameState.players.end()) {
			gameState.players.erase(i);
		}

		if (pendingViewer) {
			for (int i=1; i<=MAX_NUMBER_OF_TOTAL_CONNECTIONS; i++) {
				if (gameState.connectionStatus[i] == CONNECTION_VIEWER_STATUS) {
					gameState.connectionStatus[i] = CONNECTION_PLAYER_STATUS;
					gameState.players.push_back(i);
					Transmission* playerGameStatusTransmission = new Transmission(OPCODE_PLAYER_GAME_STATUS, false);
					playerGameStatusTransmission->setTotalBytes(OPCODE_PLAYER_GAME_STATUS_LENGTH);
					createPlayerGameStatusMessage(playerGameStatusTransmission->getBuffer(), CONNECTION_NEWPLAYER_STATUS);
					gameState.sendTranmissions[i].push_back(playerGameStatusTransmission);
					break;
				}
			}
		}

	}

	while (!gameState.sendTranmissions[(int)clientId].empty()) {
		Transmission* transmission = gameState.sendTranmissions[(int)clientId].front();
		gameState.sendTranmissions[(int)clientId].pop_front();
		delete transmission;
	}
	delete gameState.reciveTranmissions[(int)clientId];
	gameState.reciveTranmissions[(int)clientId] = 0;


	int status = close(gameState.clientSockets[(int)clientId]);
	if (status == -1) {
		// nothing much we can do about it
	}

	gameState.clientSockets[(int)clientId] = -1;
	gameState.connectionStatus[(int)clientId] = CONNECTION_DISCONNECTED_STATUS;
	gameState.numberOfClients -= 1;

}

/* message generators */

void createBooleanMessage(char buffer[], int opCode, bool boolean) {
	buffer[0] = (char)opCode;
	buffer[1] = boolean ? TRUE : FALSE;
}

void createAckMessage(char buffer[], bool isLegalMove) {
	createBooleanMessage(buffer,OPCODE_ACK,isLegalMove);
}

void createGameTypeMessage(char buffer[], bool isMisere) {
	createBooleanMessage(buffer,OPCODE_GAME_TYPE,isMisere);
}

/* status: 0(lose), 1(win), 2(game over, for viewers) */
void createGameEndStatusMessage(char buffer[], byte status) {
	buffer[0] = OPCODE_GAME_END_STATUS;
	buffer[1] = status;
}

void createServerChatMessage(char buffer[], byte sender, int messageLength, char* message) {
	buffer[0] = OPCODE_SERVER_MESSAGE;
	buffer[1] = sender;
	memcpy(buffer+2, &messageLength, sizeof(int32_t));
	memcpy(buffer+2+sizeof(int32_t), message, messageLength);
}

void createYourTurnMessage(char buffer[]) {
	buffer[0] = OPCODE_YOUR_TURN;
}

void createPlayerStatusMessage(char buffer[], byte numberOfPlayers, byte clientId) {
	buffer[0] = OPCODE_PLAYER_STATUS;
	buffer[1] = numberOfPlayers;
	buffer[2] = clientId;
}

/* status: 0(on connection - viewer), 1(on connection - player), 2(during game - player) */
void createPlayerGameStatusMessage(char buffer[], byte status) {
	buffer[0] = OPCODE_PLAYER_GAME_STATUS;
	buffer[1] =	status;
}

void createIllegalNotInTurnMessage(char buffer[]) {
	buffer[0] = OPCODE_ILLEGAL_NOT_IN_TURN_MESSAGE;
}

void createClientRejectedMessage(char buffer[]) {
	buffer[0] = OPCODE_CLIENT_REJECTED;
}

void createStatusMessage(char buffer[], const int32_t heaps[]) {
	buffer[0] = OPCODE_STATUS;
	memcpy(buffer+1, heaps, NUMBER_OF_HEAPS * sizeof(int32_t));
}

/* game logic */

void populateWithRandomHeaps(struct game_state& gameState) {
    srand(time(NULL));
    for (int i = 0; i<NUMBER_OF_HEAPS; i++) {
		gameState.heaps[i] = rand() % gameState.heapSizeLimit + 1;
	}
}

bool isLegalMove(struct game_state& gameState, int chosenHeap, int32_t numberOfRemovedObjects) {
	if (chosenHeap < 0 || chosenHeap > NUMBER_OF_HEAPS-1) {
		return false;
	}

	if (numberOfRemovedObjects < 1 || numberOfRemovedObjects > gameState.heaps[chosenHeap]) {
		return false;
	}

	return true;
}

bool isGameEnd(struct game_state& gameState) {
	for (int i=0; i<NUMBER_OF_HEAPS; i++) {
		if (gameState.heaps[i] > 0) {
			return false;
		}
	}
	return true;
}
